use gio::prelude::*;
//use glib::clone;
use gtk::gdk_pixbuf::{Colorspace, Pixbuf};
use glib::clone;
use gtk::prelude::*;
// use shotgun;
use color_blinder::RgbaBuf;
use gtk::Image;
use image::GenericImageView;
//use parking_lot::RwLock;
use std::cell::RefCell;
use std::rc::Rc;
//use std::sync::Arc;
//use lazy_static::lazy_static;
use std::time::Instant;
use std::time::Duration;
use x11::xlib::XID;

// mod window_list;
use super::window_list;

const UPDATE_INTERVAL_MS: u64 = 2 * 1000;
const MAGIC_MULTIPLICATION: i32 = 4;
const BOX_SPACING: i32 = 8;
//const CHANNEL_ERROR: &str = "unable to take() from glib channel";

/// Zoom percentage on each level
const ZOOM_LEVELS: [(u32, &'static str); 12] = [
    (10, "10%"),
    (12, "12%"),
    (15, "15%"),
    (18, "18%"),
    (20, "20%"),
    (25, "25%"),
    (33, "33%"),
    (42, "42%"),
    (50, "50%"),
    (66, "66%"),
    (75, "75%"),
    (100, "100%"),
];
const FILTER_FILTER_TEXTS: [(&'static str, Option<color_blinder::FilterKind>); 12] = [
    ("Combined", None),
    (
        "Achromatomaly",
        Some(color_blinder::FilterKind::ACHROMATOMALY),
    ),
    (
        "Achromatopsia",
        Some(color_blinder::FilterKind::ACHROMATOPSIA),
    ),
    (
        "Deuteranomaly",
        Some(color_blinder::FilterKind::DEUTERANOMALY),
    ),
    (
        "Deuteranopia",
        Some(color_blinder::FilterKind::DEUTERANOPIA),
    ),
    (
        "DeuteranopiaBVM97",
        Some(color_blinder::FilterKind::DEUTERANOPIABVM97),
    ),
    ("Protanomaly", Some(color_blinder::FilterKind::PROTANOMALY)),
    ("Protanopia", Some(color_blinder::FilterKind::PROTANOPIA)),
    (
        "ProtanopiaBVM97",
        Some(color_blinder::FilterKind::PROTANOPIABVM97),
    ),
    ("Tritanomaly", Some(color_blinder::FilterKind::TRITANOMALY)),
    ("Tritanopia", Some(color_blinder::FilterKind::TRITANOPIA)),
    (
        "TritanopiaBVM97",
        Some(color_blinder::FilterKind::TRITANOPIABVM97),
    ),
];

/*
lazy_static! {
    static ref ZOOM_LEVELS_LIST: gtk::ListStore = {
        let mut list = gtk::ListStore::new(&[Type::Uint, Type::String]);
        //let mut iter = list.iter();
        let col_indices = [0];
        for (v, s) in ZOOM_LEVELS {
            list.set(&list.append(), &col_indices, &[v, s]);
        }
        list
    };
}
*/

enum ZoomKind {
    Specific { percentage: usize },
    FitWidth { last_percentage: usize },
}

pub struct App {
    window_list: window_list::WindowList,
    render_context: color_blinder::ProcessingContext,
    last_screen_capture: RgbaBuf,
    pixbuf: Pixbuf,
    image: Image,
    update_label: gtk::Label,
    fit_width_button: gtk::Button,
    filter_filter: color_blinder::FilterKind,
    filter_filter_combobox: gtk::ComboBoxText,
    targets_combobox: gtk::ComboBoxText,
    target_selected: Option<XID>,
    zoom_label: gtk::Label,
    //pub zoom_dropdown: (gtk::ListStore, gtk::ComboBox),
    zoom_decrease: gtk::Button,
    zoom_increase: gtk::Button,
    zoom_level_index: ZoomKind,
    window: gtk::ApplicationWindow,
    pub icon_data: RgbaBuf,
    pub content_box: gtk::Box,
    pub menu_box: gtk::Box,
    //sender: Sender<Action>,
    //receiver: RwLock<Option<Receiver<Action>>>,
}

impl App {
    pub fn new(application: &gtk::Application) -> Self {
        //let (sender, r) = glib::MainContext::channel(glib::PRIORITY_DEFAULT);
        //let receiver = RwLock::new(Some(r));

        let window = gtk::ApplicationWindow::new(application);
        add_actions(application, &window);

        let mut icon_data = image::load_from_memory_with_format(
            include_bytes!("./public/logo.png"),
            image::ImageFormat::Png,
        )
        .expect("unable to parse icon_data")
        .to_rgba8();
        // let icon_buf = Pixbuf::
        //     //from_bytes(&glib::Bytes::from_static(&icon_data), Colorspace::Rgb, true, 8, 512, 512, MAGIC_MULTIPLICATION * 512)
        //     from_mut_slice(&mut *icon_data, Colorspace::Rgb, true, 8, 512, 512, MAGIC_MULTIPLICATION * 512)
        //     //from_inline(include_bytes!("../public/logo.png"), false).expect("unable to create icon")
        // ;
        // window.set_icon(Some(&icon_buf));

        let content_box = gtk::Box::new(gtk::Orientation::Vertical, 0);
        window.set_child(Some(&content_box));
        let menu_box = gtk::Box::new(gtk::Orientation::Horizontal, BOX_SPACING);
        content_box.append(&menu_box);

        let zoom_decrease = gtk::Button::with_label("-");
        menu_box.append(&zoom_decrease);

        let zoom_label = gtk::Label::new(None);
        menu_box.append(&zoom_label);

        //let zoom_dropdown = setup_dropdown(&menu_box);

        let zoom_increase = gtk::Button::with_label("+");
        menu_box.append(&zoom_increase);

        let fit_width_button = gtk::Button::with_label("Fit Width");
        menu_box.append(&fit_width_button);

        // -------------------------------------
        let separator = gtk::Separator::new(gtk::Orientation::Vertical);
        menu_box.append(&separator);

        let filter_filter_combobox = gtk::ComboBoxText::new();
        menu_box.append(&filter_filter_combobox);
        for t in FILTER_FILTER_TEXTS.iter() {
            filter_filter_combobox.append_text(t.0);
        }
        filter_filter_combobox.set_active(Some(0));

        // -------------------------------------
        let separator = gtk::Separator::new(gtk::Orientation::Vertical);
        menu_box.append(&separator);

        let select_label = gtk::Label::new(Some(&"Select target:"));
        menu_box.append(&select_label);

        let targets_combobox = gtk::ComboBoxText::new();
        menu_box.append(&targets_combobox);

        let update_label = gtk::Label::new(None);
        menu_box.append(&update_label);

        let (width, height) = (480i32, 420i32);
        let mut last_screen_capture = RgbaBuf::from_pixel(
            width as u32,
            height as u32,
            image::Rgba::<u8>([0xff, 0x0f, 0xff, 0xff]),
        );
        let stride = MAGIC_MULTIPLICATION * width;
        let mut pixbuf = Pixbuf::from_mut_slice(
            &mut *last_screen_capture,
            Colorspace::Rgb,
            true,
            8,
            width,
            height,
            stride,
        );

        let image = gtk::Image::from_pixbuf(Some(&mut pixbuf));
        content_box.append(&image);

        let render_context = color_blinder::Config {
            combine_output: false,
            render_label: true,
            //processing: color_blinder::ProcessingStyle::MustOffload(None),
            processing: color_blinder::ProcessingStyle::MayOffload(None),
            //processing: color_blinder::ProcessingStyle::Inline,
        }
        .into_context();

        let app = Self {
            window,
            window_list: window_list::WindowList::new(),
            icon_data,
            content_box,
            menu_box,
            zoom_decrease,
            zoom_increase,
            zoom_label,
            //zoom_dropdown,
            zoom_level_index: ZoomKind::FitWidth { last_percentage: 4 },
            targets_combobox,
            target_selected: None,
            update_label,
            fit_width_button,
            filter_filter: color_blinder::FilterKind::all(),
            filter_filter_combobox,
            //sender,
            //receiver,
            image,
            pixbuf,
            last_screen_capture,
            render_context,
            //sync_source_id: RwLock::new(None),
            //threadpool,
            //shutdown_in_progress,
            //features,
        };

        app
    }

    /*
    fn run(&self, app: Rc<Self>) {
        let receiver = self.receiver.write().take().expect(CHANNEL_ERROR);
        receiver.attach(None, move |action| app.process_action(action));
    }
    */

    /*
    fn process_action(&self, action: Action) -> glib::Continue {
        use Action::*;

        match action {
            RefreshCapture => { /*self.retake_screen_capture()*/ }
        }

        glib::Continue(true)
    }
    */

    fn obtain_pixbuf_screenshot(&mut self) {
        // let pixels = shotgun::capture(self.target_selected, None).unwrap_or_else(|e| {
        //     image::DynamicImage::ImageRgba8(color_blinder::labels::render(&format!(
        //         "unable to capture screen: {:?}",
        //         e
        //     )))
        // });

        // let (mut max_width, mut max_height) = match self.zoom_level_index {
        //     ZoomKind::Specific { percentage } => {
        //         let new_max = pixels.width().max(pixels.height()) * ZOOM_LEVELS[percentage].0 / 100;
        //         (new_max, new_max)
        //     }
        //     ZoomKind::FitWidth { .. } => {
        //         let width=self.window.width();
        //         // let (width, height) = self.window.size();
        //         let height=self.window.height();
        //         (width as u32, height as u32)
        //     }
        // };

        // let is_combined = self.filter_filter.bits().count_ones() > 1;
        // self.render_context.combine_output(is_combined);
        // if is_combined {
        //     max_width = (max_width - 5 * color_blinder::COMBINED_MARGIN) / 4;
        //     max_height = (max_height - 4 * color_blinder::COMBINED_MARGIN) / 3;
        // }

        // let pixels = pixels.thumbnail(max_width, max_height).into_rgba8();

        // let images = self
        //     .render_context
        //     .process(pixels, self.filter_filter)
        //     .expect("unable to generate altered image");

        // self.last_screen_capture = images.into_iter().next().map(|t| t.1).unwrap();

        // let (width, height) = (
        //     self.last_screen_capture.width() as i32,
        //     self.last_screen_capture.height() as i32,
        // );
        // let stride = MAGIC_MULTIPLICATION * width as i32;

        // self.pixbuf = Pixbuf::from_mut_slice(
        //     &mut *self.last_screen_capture,
        //     //&*pixels.as_ref(),
        //     Colorspace::Rgb,
        //     true,
        //     8,
        //     width,
        //     height,
        //     stride,
        // );
    }

    fn retake_screen_capture(&mut self) {
        let start = Instant::now();
        self.update_label.set_label("Updating ...");

        self.obtain_pixbuf_screenshot();
        self.image.set_from_pixbuf(Some(&self.pixbuf));

        let s = format!("Auto Updated in {:2?}", start.elapsed());
        self.update_label.set_label(&*s);
    }

    fn update_zoom_label(&self) {
        use ZoomKind::*;
        match self.zoom_level_index {
            Specific { percentage } => {
                self.zoom_label.set_label(&ZOOM_LEVELS[percentage].1);
                self.fit_width_button.show();
            }
            FitWidth { last_percentage } => {
                let s = format!("Fit width (last: {})", ZOOM_LEVELS[last_percentage].1);
                self.zoom_label.set_label(&*s);
                self.fit_width_button.hide();
            }
        };
    }

    fn update_zoom_fit_width(&mut self) {
        use ZoomKind::*;
        self.zoom_level_index = match self.zoom_level_index {
            Specific { percentage } => FitWidth {
                last_percentage: percentage,
            },
            FitWidth { .. } => unreachable!("App::update_zoom_fit_width(FitWidth{ .. })"),
        };
        self.update_zoom_label();
    }

    fn update_zoom_buttons(&mut self, next: usize) {
        const MAX_INDEX: usize = ZOOM_LEVELS.len() - 1;

        // disable at the end
        self.zoom_increase.set_sensitive(next != MAX_INDEX);
        // disable at the front
        self.zoom_decrease.set_sensitive(next != 0);

        self.zoom_level_index = ZoomKind::Specific { percentage: next };
        self.update_zoom_label();
    }

    fn increase_zoom(&mut self) {
        use ZoomKind::*;
        let current = match self.zoom_level_index {
            Specific { percentage } => percentage,
            FitWidth { last_percentage } => last_percentage,
        };
        let next = current + 1;
        const LEN: usize = ZOOM_LEVELS.len();
        assert!(next < LEN, "increase_zoom index {} overrun {}", next, LEN);

        self.update_zoom_buttons(next);
    }

    fn decrease_zoom(&mut self) {
        use ZoomKind::*;
        let current = match self.zoom_level_index {
            Specific { percentage } => percentage,
            FitWidth { last_percentage } => last_percentage,
        };
        assert!(current != 0, "decrease_zoom index {} underrun", current - 1);

        self.update_zoom_buttons(current - 1);
    }

    fn setup_targets(&mut self) {
        let ref bc = self.targets_combobox;

        if self.window_list.update() {
            bc.remove_all();

            bc.append_text("Whole screen");
            bc.append_text("---------------");

            for wi in &self.window_list.results {
                bc.append_text(&wi.title);
            }

            self.update_target_selection();
        }
    }

    fn update_target(&mut self, index: u32) {
        self.target_selected = self
            .window_list
            .results
            .iter()
            .enumerate()
            .filter(|(i, _)| *i as u32 + 2 == index)
            .map(|(_, wi)| wi.id)
            .next();

        //println!("update_target = {:?}", self.target_selected);
        self.update_target_selection();
    }

    fn update_target_selection(&mut self) {
        let i = match self.target_selected {
            None => 0,
            Some(id) => self
                .window_list
                .results
                .iter()
                .enumerate()
                .filter(|(_, wi)| wi.id == id)
                .map(|(i, _)| i as u32 + 2)
                .next()
                .unwrap_or(0),
        };

        self.targets_combobox.set_active(Some(i));
    }
}

/*
pub enum Action {
    RefreshCapture,
}
*/

pub fn main() {
    //let application = gtk::Application::new(Some("ch.estada.color_blinder"), Default::default())
    //.expect("Initialization failed...");
    //let menubar = gtk::MenuBar::new();
    //let menubar = gtk::MenuButtonBuilder::new().label("menu").build();
        // let app = Application::builder()
        // .application_id("org.gtk-rs.example")
        // .build();
    let application = gtk::Application::builder()
        .application_id("ch.estada.color_blinder")
        .flags(Default::default())
        .build();
    let my_app = Rc::new(RefCell::new(None::<App>));

    application.connect_startup(|app| {
        add_accelerators(app);
        /*
                let screen = gdk::Display::get_n_monitors();
                let windows = screen.get_window_stack();
        */
    });

    // Create a new application
    application.connect_activate(clone!(@weak my_app => move |app| {
        let mut new_app = App::new(app);

        // new_app.window.show_all();
        new_app.window.present();

        new_app.retake_screen_capture();
        new_app.update_zoom_label();
        new_app.setup_targets();

        new_app.zoom_increase.connect_clicked(clone!(@weak my_app => move |_|{
            //use std::ops::DerefMut;

            let mut app = my_app.borrow_mut();
            if let Some(ref mut app) = *app {
                app.increase_zoom();
            }
        }));

        new_app.zoom_decrease.connect_clicked(clone!(@weak my_app => move |_|{
            let mut app = my_app.borrow_mut();
            if let Some(ref mut app) = *app {
                app.decrease_zoom();
            }
        }));

        new_app.fit_width_button.connect_clicked(clone!(@weak my_app => move |_|{
            let mut app = my_app.borrow_mut();
            if let Some(ref mut app) = *app {
                app.update_zoom_fit_width();
            }
        }));

        new_app.filter_filter_combobox.connect_changed(clone!(@weak my_app => move |cbt|{
            if let Some(index) = cbt.active() {
                let mut app = my_app.borrow_mut();
                if let Some(ref mut app) = *app {
                   app.filter_filter =  match FILTER_FILTER_TEXTS[index as usize].1 {
                        Some(filter) => filter,
                        None => color_blinder::FilterKind::all(),
                    };
                }
            }
        }));

        new_app.targets_combobox.connect_changed(clone!(@weak my_app => move |cbt|{
            if let Some(index) = cbt.active() {
                let app = my_app.try_borrow_mut();
                if let Ok(mut app) = app {
                    if let Some(ref mut app) = *app {
                        app.update_target(index);
                    }
                } else {
                    println!("targets_combobox.connect_changed: Lost event!");
                }
            }
        }));

        //new_app.window.get_header_bar();

        {
            let my_app = my_app.clone();
            glib::timeout_add_local(Duration::from_millis(UPDATE_INTERVAL_MS), move || -> glib::Continue {
                //use std::ops::DerefMut;

                let mut app = my_app.borrow_mut();
                if let Some(ref mut app) = *app {
                    app.retake_screen_capture();
                    app.setup_targets();
                }

                glib::Continue(true)
            });
        }

        *my_app.borrow_mut() = Some(new_app);
    }));

    // Run the application
    application.run();//&std::env::args().collect::<Vec<_>>());
}

fn add_accelerators(application: &gtk::Application) {
    //application.set_accels_for_action("app.about", &["F1"]);

    // `Primary` is a platform-agnostic accelerator modifier.
    // On Windows and Linux, `Primary` maps to the `Ctrl` key,
    // and on macOS it maps to the `command` key.
    application.set_accels_for_action("app.quit", &["<Primary>Q", "Escape"]);
}
/// This function creates "actions" which connect on the declared actions from the menu items.
fn add_actions(application: &gtk::Application, window: &gtk::ApplicationWindow) {
    let quit = gio::SimpleAction::new("quit", None);
    quit.connect_activate(clone!(@weak window => move |_, _| {
        window.close();
    }));

    // We need to add all the actions to the application so they can be taken into account.
    application.add_action(&quit);
}

/*
fn setup_dropdown(menu_box: &gtk::Box) -> (gtk::ListStore, gtk::ComboBox) {
    let list = gtk::ListStore::new(&[Type::U32, Type::String]);
    //let mut iter = list.iter();
    //let parent = None;
    let col_indices = [0, 1];
    for (v, s) in &ZOOM_LEVELS {
        //list.insert_with_values(parent, &col_indices, &[&v, &s]);
        list.set(&list.append(), &col_indices, &[&v, &s]);
    }

    let cb = gtk::ComboBox::with_model(&list);
    //cb.set_id_column(0);
    //cb.set_entry_text_column(1);
    //cb.set_active(Some(0));

    cb.connect_changed(|cb| {
        println!("ComboBox change");
    });

    // TODO: menu_box.add(&cb);

    return (list, cb);
}
*/
